use std::collections::HashMap;

type Data = HashMap<String, u32>;

trait Formatter {
    fn format(&self, data: &Data, buf: &mut String);
}

struct Report;

impl Report {
    // Write should be used but we kept it as String to ignore error handling.
    fn generate<T: Formatter>(formatter: T, buffer: &mut String) {
        // Backend operations...
        let mut data = HashMap::new();
        data.insert("one".to_string(), 1);
        data.insert("two".to_string(), 2);

        // Generate report.
        formatter.format(&data, buffer);
    }
}

struct Text;

impl Formatter for Text {
    fn format(&self, data: &Data, buf: &mut String) {
        for (key, val) in data {
            let entry = format!("{key} {val}\n");
            buf.push_str(&entry);
        }
    }
}

struct Json;

impl Formatter for Json {
    fn format(&self, data: &Data, buf: &mut String) {
        buf.push('[');
        for (key, val) in data.iter() {
            let entry = format!(r#"{{"{}":"{}"}}"#, key, val);
            buf.push_str(&entry);
            buf.push(',');
        }
        if !data.is_empty() {
            // Remove an extra comma at the end.
            buf.pop();
        }
        buf.push(']');
    }
}

fn main() {
    {
        // In this example our invariants (or abstractions) are Formatter and
        // Report, while Text and Json are our strategy structs. These strategies
        // have to implement the Formatter trait.
        let mut buffer = String::from("");
        Report::generate(Text, &mut buffer);
        assert!(buffer.contains("one 1"));
        assert!(buffer.contains("two 2"));

        // Reuse the same buffer.
        buffer.clear();

        Report::generate(Json, &mut buffer);
        assert!(buffer.contains(r#"{"one":"1"}"#));
        assert!(buffer.contains(r#"{"two":"2"}"#));
    }

    {
        // We don’t need to use traits in order to design this pattern in Rust.

        // The following toy example demonstrates the idea of the Strategy pattern
        // using Rust closures.
        struct Adder;

        impl Adder {
            pub fn add<F>(x: u8, y: u8, f: F) -> u8
            where
                F: Fn(u8, u8) -> u8,
            {
                f(x, y)
            }
        }

        let arith_adder = |x, y| x + y;
        let bool_adder = |x, y| {
            if x == 1 || y == 1 {
                1
            } else {
                0
            }
        };
        let custom_adder = |x, y| 2 * x + y;

        assert_eq!(9, Adder::add(4, 5, arith_adder));
        assert_eq!(0, Adder::add(0, 0, bool_adder));
        assert_eq!(5, Adder::add(1, 3, custom_adder));
    }

    {
        // In fact, Rust already uses this idea (of the Strategy pattern using
        // Rust closures) for Options’s map method.
        let val = Some("Rust");

        let len_strategy = |text: &str| text.len();
        assert_eq!(4, val.map(len_strategy).unwrap());

        let first_byte_strategy = |text: &str| text.bytes().next().unwrap();
        // The decimal ASCII code for 'R' is 82.
        assert_eq!(82, val.map(first_byte_strategy).unwrap());
    }
}
